﻿using System.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System;

namespace Mesuro.Common {
	public interface ISeries : IEnumerable<Vector> {
		IChannelCollection Channels { get; }
		string Name { get; set; }
		int Dimensions { get; }
		event NotifySeriesChangedEventHandler DataChanged;
	}
	public class DefaultImmutableSeries : ISeries {

		public static readonly DefaultImmutableSeries EmptySeries = new DefaultImmutableSeries( new DefaultChannelCollection(), new Vector[0] );

		private readonly IChannelCollection _channelCollection;
		private readonly IEnumerable<Vector> _data;

		public IChannelCollection Channels {
			get { return _channelCollection; }
		}

		public string Name { get; set; }

		public int Dimensions {
			get { return _channelCollection.Count; }
		}

		public event NotifySeriesChangedEventHandler DataChanged;

		public DefaultImmutableSeries( IChannelCollection cc, IEnumerable<Vector> data ) {
			_channelCollection = cc;
			_data = data;
		}

		public IEnumerator<Vector> GetEnumerator() {
			return _data.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}
	}
	public class DefaultMutableSeries : ISeries {
		DefaultChannelCollection _channelCollection = new DefaultChannelCollection();
		private LinkedList<Vector> data = new LinkedList<Vector>();

		public IChannelCollection Channels {
			get { return _channelCollection; }
		}

		public string Name { get; set; }

		public int DataPoints { get { return data.Count; } }

		public int Dimensions {
			get { return _channelCollection.Count; }
		}

		public event NotifySeriesChangedEventHandler DataChanged;
		private void RaiseDataChanged( NotifySeriesChangedEventArgs e ) {
			if (DataChanged != null)
				DataChanged( this, e );
		}

		public DefaultMutableSeries( params string[] channelNames ) {
			foreach (string name in channelNames) {
				AddChannel( new DefaultMutableChannel( name, new Limits() ) );
			}
		}

		public void AddDatum( Vector datum ) {
			datum = datum.Top( Channels.Count );
			data.AddLast( datum );
			for (int i = 0; i < Channels.Count; i++)
				((DefaultMutableChannel)Channels[i]).Limits = Limits.Union( Channels[i].Limits, datum[i] );
			RaiseDataChanged( new NotifySeriesChangedEventArgs( this, NotifySeriesChangedAction.DataAdded ) );
		}

		public void AddChannel( IChannel channel ) {
			if (data.Count != 0)
				throw new InvalidOperationException( "Cannot add a empty channel to a series after data has been added." );
			_channelCollection.AddChannel( channel );
		}

		public void SortBy( IChannel channel, IComparer<double> comparator = null ) {
			int idx = _channelCollection.IndexOf( channel );
			IOrderedEnumerable<Vector> o = (comparator == null) ? data.OrderBy( ( vec ) => vec[idx] ) : data.OrderBy( ( vec ) => vec[idx], comparator );

			data.Clear();

			foreach (Vector row in o)
				data.AddLast( row );
		}

		public DefaultImmutableSeries SortedBy( IChannel channel, IComparer<double> comparator = null ) {
			int idx = _channelCollection.IndexOf( channel );
			if (comparator == null)
				return new DefaultImmutableSeries( _channelCollection, data.OrderBy( ( ch ) => ch[idx] ) );
			else
				return new DefaultImmutableSeries( _channelCollection, data.OrderBy( ( ch ) => ch[idx], comparator ) );
		}

		public IEnumerator<Vector> GetEnumerator() {
			return data.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}
	}
	public class NotifySeriesChangedEventArgs : EventArgs {
		public ISeries Source { get; private set; }
		public NotifySeriesChangedAction Action { get; private set; }
		public NotifySeriesChangedEventArgs( ISeries source, NotifySeriesChangedAction action ) {
			this.Source = source;
			this.Action = action;
		}
	}
	public enum NotifySeriesChangedAction { ChannelsChanged, DataReplaced, DataAdded }
	public delegate void NotifySeriesChangedEventHandler( object sender, NotifySeriesChangedEventArgs e );
}
